/*********************************************************************************

VCD2TXT - convert vcd waveforms to plain text
Copyright (C) 2009 Radu Stefan

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*********************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <string>
#include <map>
#include <ctype.h>
#include <vector>

using namespace std;

int listlen1=0;
int listlen2=0;
int idcount=0;
int donevars=0;
int hasspec=0;

long long globaltime=-1;

struct vardesc
{
	string path;
	string name;
	string typs;
	int typ;
	int listpos;
	int id;
	int size;
};

string mstack="";
vector<int> mstackpos;
vector<char> values;
vector<double> valuesr;
vector<string> specifiers;
typedef vector<int> intvector;
vector<intvector> bits;

map<string,vardesc> varnames;

enum tokens {TOK_EOF, TOK_UNK, TOK_ERR,
	TOK_TIME,
	TOK_COMMENT, TOK_DATE, TOK_END, TOK_ENDDEFINITIONS, TOK_SCOPE, TOK_TIMESCALE, TOK_UPSCOPE, TOK_VAR, TOK_VERSION,
	TOK_DUMPALL, TOK_DUMPOFF, TOK_DUMPON, TOK_DUMPVARS,
	TOK_STRING};

class rreader
{
public:
	rreader():fid(-1){;}
	~rreader() { if (fid>=0) close(fid); };

	char data[65540]; // there is a hard limit for any token at 32768
	int inbuffer;
	int eofreached;
	int pos;
	int mode;

	string lasttok;
	long long lasttime;

	void openfile(char *fname)
	{
		fid=open(fname,O_RDONLY);
		inbuffer=0;
		eofreached=0;
		pos=0;
		mode=0;
	}

	void fillbuffer()
	{
		int i,len;
		if (eofreached) return;
		if (inbuffer-pos>32768) return;
		for (i=pos; i<inbuffer; i++) data[i-pos]=data[i];
		inbuffer-=pos;
		pos=0;
		data[inbuffer]=0;
		if (fid<0) return;
		len=read(fid,data+inbuffer,65536-inbuffer);
		if (!len) eofreached=0;
		inbuffer+=len;
	}

	int readtok();

	int fid;
};

int rreader::readtok()
{
	int hadspace=1;
	fillbuffer();
	lasttok="";
	if (mode==1 || mode==2) // look for $end and nothing else
	{
		while (pos<inbuffer)
		{
			if (0==strncmp(data+pos,"$end",4))
			{
				pos+=4;
				mode=0;
				return TOK_STRING;
			}
			if (data[pos]==10 || data[pos]==13)
			{
				if (mode==2) lasttok+="\\n";
				else if (!hadspace) lasttok+=" ";
				hadspace=1;
				pos++;
			} else {
				if (mode!=1 || !hadspace || !isspace(data[pos]))
					lasttok+=data[pos];
				hadspace=isspace(data[pos]);
				pos++;
			}
		}
		return TOK_ERR;
	}
	while (pos<inbuffer && isspace(data[pos])) pos++;
	if (pos>=inbuffer) return TOK_EOF;
	while (pos<inbuffer && !isspace(data[pos])) lasttok+=data[pos++];
	if (lasttok[0]=='#')
	{
		lasttime=atoll(lasttok.c_str()+1);
		return TOK_TIME;
	}
	if (mode==3) { mode=0; return TOK_STRING; }
	if (lasttok[0]!='$') return TOK_STRING;
	if (0==strcmp(lasttok.c_str(),"$comment")) { mode=2; return TOK_COMMENT; }
	if (0==strcmp(lasttok.c_str(),"$version")) { mode=2; return TOK_VERSION; }
	if (0==strcmp(lasttok.c_str(),"$date")) { mode=1; return TOK_DATE; }
	if (0==strcmp(lasttok.c_str(),"$timescale")) { mode=1; return TOK_TIMESCALE; }
	if (0==strcmp(lasttok.c_str(),"$enddefinitions")) return TOK_ENDDEFINITIONS;
	if (0==strcmp(lasttok.c_str(),"$scope")) return TOK_SCOPE;
	if (0==strcmp(lasttok.c_str(),"$upscope")) return TOK_UPSCOPE;
	if (0==strcmp(lasttok.c_str(),"$var")) return TOK_VAR;
	if (0==strcmp(lasttok.c_str(),"$end")) return TOK_END;
	if (0==strcmp(lasttok.c_str(),"$dumpall")) return TOK_DUMPALL;
	if (0==strcmp(lasttok.c_str(),"$dumpoff")) return TOK_DUMPOFF;
	if (0==strcmp(lasttok.c_str(),"$dumpon")) return TOK_DUMPON;
	if (0==strcmp(lasttok.c_str(),"$dumpvars")) return TOK_DUMPVARS;

	return TOK_UNK;
}

rreader a;
FILE *fou;

void dispvariables()
{
	int i,j;
	if (!donevars) return;
	if (hasspec)
	{
		for (i=0; i<specifiers.size(); i++)
		{
			long long v=0;
			if (bits[i].size()==0) { fprintf(fou,"%16lld ",globaltime); continue; }
			for (j=0; j<bits[i].size(); j++)
			{
				if (values[bits[i][j]]=='1') v=v*2+1;
				else v=v*2;
			}
			fprintf(fou,specifiers[i].c_str(),v);
		}
		fprintf(fou,"\n");
		return;
	}
	fprintf(fou,"%16lld ",globaltime);
	for (i=0; i<listlen1; i++) fprintf(fou,"%c",values[i]);
	for (i=0; i<listlen2; i++) fprintf(fou," %g",valuesr[i]);
	fprintf(fou,"\n");
}

int main(int argc, char *argv[])
{
	if (argc<3 || argc>4)
	{
		printf("Usage: vcd2txt <input.vcd> <output.txt> [<format.txt>]\n");
		printf("format specification: on each line:\n");
		printf(" <printf format specifier> <space separated list of bits starting with MSB>\n");
		return 1;
	}
	a.openfile(argv[1]);
	if (a.fid<0)
	{
		printf("ERROR: cannot open input file: %s\n",argv[1]);
		return 1;
	}
	if (0==strcmp(argv[2],"-")) fou=fdopen(1,"w");
	else fou=fopen(argv[2],"w");
	if (!fou)
	{
		printf("ERROR: cannot open  output file: %s\n",argv[2]);
		return 1;
	}
	if (argc==4)
	{
		FILE *fspec=fopen(argv[3],"r");
		if (!fspec)
		{
			printf("ERROR: cannot open  format file: %s\n",argv[3]);
			return 1;
		}
		hasspec=1;

		while (!feof(fspec))
		{
			int cnt,v;
			char fmt[68];
			cnt=fscanf(fspec,"%64s",fmt);
			if (!cnt) break;
			//printf("fmt=%s	",fmt);
			specifiers.push_back(fmt);
			specifiers.back()+=" ";
			bits.push_back(intvector());
			while (1)
			{
				cnt=fscanf(fspec,"%64[ \t]",fmt);
				//printf("space count: %d\n",cnt);
				if (fscanf(fspec,"%64[\n\r]",fmt)) break;
				cnt=fscanf(fspec,"%d",&v);
				if (cnt!=1)
				{
					printf("error reading format file, expecting number\n");
					return 1;
				}
				bits.back().push_back(v);
			}
		}
	}
	while (1)
	{
		int tok=a.readtok();
		if (tok==TOK_DATE)
		{
			if (a.readtok()!=TOK_STRING) fprintf(stderr,"ERROR: expecting string after $date\n\n");
			fprintf(fou,"DATE:      %s\n",a.lasttok.c_str());
			continue;
		}
		if (tok==TOK_VERSION)
		{
			if (a.readtok()!=TOK_STRING) fprintf(stderr,"ERROR: expecting string after $date\n\n");
			fprintf(fou,"VERSION:   %s\n",a.lasttok.c_str());
			continue;
		}
		if (tok==TOK_COMMENT)
		{
			if (a.readtok()!=TOK_STRING) fprintf(stderr,"ERROR: expecting string after $date\n\n");
			fprintf(fou,"COMMENT:   %s\n",a.lasttok.c_str());
			continue;
		}
		if (tok==TOK_TIMESCALE)
		{
			if (a.readtok()!=TOK_STRING) fprintf(stderr,"ERROR: expecting string after $date\n\n");
			fprintf(fou,"TIMESCALE: %s\n",a.lasttok.c_str());
			continue;
		}
		if (tok==TOK_SCOPE)
		{
			if (a.readtok()!=TOK_STRING) { fprintf(stderr,"ERROR: bad scope definition\n\n"); continue; }
			if (a.readtok()!=TOK_STRING) { fprintf(stderr,"ERROR: bad scope definition\n\n"); continue; }
			mstackpos.push_back(mstack.length());
			mstack=mstack+a.lasttok+".";
			while (1) { int tok=a.readtok(); if (tok==TOK_END || tok==TOK_EOF) break;}
			continue;
		}
		if (tok==TOK_UPSCOPE)
		{
			int q=mstackpos.back();
			mstackpos.pop_back();
			mstack=mstack.substr(0,q);
			while (1) { int tok=a.readtok(); if (tok==TOK_END || tok==TOK_EOF) break;}
			continue;
		}
		if (tok==TOK_VAR)
		{
			if (donevars) { fprintf(stderr,"ERROR: more variable definitions after beginning of data.\n\n"); return 1; }
			vardesc b;
			b.path=mstack;
			if (a.readtok()!=TOK_STRING) { fprintf(stderr,"ERROR: bad var definition %s\n\n",a.lasttok.c_str()); continue; }
			b.typs=a.lasttok;
			if (a.readtok()!=TOK_STRING) { fprintf(stderr,"ERROR: bad var definition %s\n\n",a.lasttok.c_str()); continue; }
			b.size=atoi(a.lasttok.c_str());
			a.mode=3;
			a.readtok();
			string hash=a.lasttok;
			b.name="";
			b.id=idcount++;
			b.typ=0;
			if (b.typs=="real") { b.typ=2; b.listpos=listlen2++; }
			else {
				if (b.size!=1) b.typ=1;
				b.listpos=listlen1;
				listlen1+=b.size;
			}
			while (1) { int tok=a.readtok(); if (tok==TOK_END || tok==TOK_EOF) break; b.name+=a.lasttok;}
			varnames[hash]=b;
			if (!hasspec) fprintf(fou,"VAR: %s,%d @%d )\n",(b.path+b.name+" ("+b.typs).c_str(),b.size,b.listpos);
			continue;
		}
		if (tok==TOK_ENDDEFINITIONS)
		{
			donevars=1;
			values.resize(listlen1);
			valuesr.resize(listlen2);
			int i;
			for (i=0; i<listlen1; i++) values[i]='X';
			for (i=0; i<listlen2; i++) valuesr[i]=0;
			if (a.readtok()!=TOK_END) { fprintf(stderr,"ERROR: bad enddefinitions\n\n"); }
			continue;
		}
		if (tok==TOK_TIME)
		{
			if (globaltime>=0)
				dispvariables();
			globaltime=a.lasttime;
			continue;
		}
		if (tok==TOK_STRING)
		{
			string s=a.lasttok;
			string hash=s.substr(1,s.length());
			int i,q;
			char c;
			if (s[0]=='b' || s[0]=='B' || s[0]=='r' || s[0]=='R') { a.readtok(); hash=a.lasttok; }
			if (varnames.find(hash)==varnames.end())
			{
				fprintf(stderr,"variable %s not found\n",hash.c_str());
				continue;
			}
			vardesc b=varnames[hash];
			if (s[0]=='b' || s[0]=='B')
			{
				for (i=s.length(),q=b.size-1; i>0 && q>=0; i--,q--)
				{
					c=values[b.listpos+q]=s[q];
				}
				if (c=='1') c='0';
				if (b.typ>=2) { fprintf(stderr,"ERROR: reading wrong type\n\n"); return 1; }
				for (;q>=0;q--) values[b.listpos+q]=c;
			} else if (b.typ==2) sscanf(s.c_str()+1,"%lg",&valuesr[b.listpos]);
			else values[b.listpos]=s[0];
			continue;
		}
		if (tok==TOK_EOF)
		{
			dispvariables();
			break;
		}
		if (!hasspec) fprintf(fou,"%d %s\n",tok,a.lasttok.c_str());
	}
	return 0;
}
